import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.GraphicsEnvironment;
import java.awt.GraphicsConfiguration;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.io.*;
import java.net.URI;
import java.util.HashSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JPanel;

import javax.swing.Timer;





public class CDShelf extends JPanel {
    private static final double ANIM_SCROLL_DELAY = 450;
    private static final int CD_SIZE = 200;
    
    private int displayWidth = CD_SIZE;
    private int displayHeight = (int) (CD_SIZE * 2 / 1.12);
    
    private Avatar[] avatars = null;
    private int count = 0;
    
    private String avatarText = null;
    
    private boolean loadingDone = false;
    
    private HashSet<String> filter;
    
    private Timer faderTimer = null;
    
    private float veilAlphaLevel = 0.0f;
    private float alphaLevel = 0.0f;
    private float textAlphaLevel = 0.0f;
    
    private int avatarIndex = -1;
    private double avatarPosition = 0.0;
    private double avatarSpacing = 0.4;
    private int avatarAmount = 5;
    private int numberOfAvatars = 0;
    private double sigma;
    private double rho;
    
    private double exp_multiplier;
    private double exp_member;
    
    private AlbumApplet parentApplet;
  
    
    private boolean damaged = true;
    
    private DrawableAvatar[] drawableAvatars;
    private Avatar[] shownAvatars;
 
   
    private final String[] filetypes = new String[] {"jpg","gif","png","jpeg"};    
    private Thread picturesLoader;
    private Thread imageGetter;
    
    private Font avatarFont;
    private CrystalCaseFactory fx;
    
    
    private volatile boolean runLoader = false;
    private boolean downloadImage = false;
    private boolean writeMp3Tag=false;
 
    
    public CDShelf() {
        avatarFont = new Font("Dialog", Font.PLAIN, 24);
        fx = CrystalCaseFactory.getInstance();
        
       filter = new HashSet<String>();
        loadAvatars();
        
        GridBagLayout layout = new GridBagLayout();
        setLayout(layout);
        
        setSigma(0.5);
        
        addComponentListener(new DamageManager());
      
        
        
        
        
        
    }
    
  
    
    public void setWriteMp3Tag(boolean writeMp3Tag) {
        this.writeMp3Tag = writeMp3Tag;
    }
    
    public void setDownloadImage(boolean downloadImage) {
        this.downloadImage = downloadImage;
    }
    public void setParent(AlbumApplet app) {
        parentApplet = app;
    }
    
    public void damaged(){
        damaged=true;
    }
    public void setAmount(int amount) {
        if (amount > avatars.length) {
            throw new IllegalArgumentException("Too many avatars");
        }
        this.avatarAmount = amount;
        repaint();
    }
    
    public Avatar getCurrentAvatar(){
        return avatars[avatarIndex];
    }
    public int getAmount(){
        return avatarAmount;
    }
    
    public int getCount(){
        return avatars.length;
    }
    
    public ActionListener getAutoScroller(DrawableAvatar avatar){
        return new AutoScroller(avatar);
    }
    
    public ActionListener getSliderScroller(int n){
        return new sliderScroller(n);
    }
    public DrawableAvatar getDrawableAvatarByIndex(int index){
         if (drawableAvatars != null) {
                for (DrawableAvatar avatar: drawableAvatars) {
                    if (avatar.index == index) {
                        return avatar;
                        
                       
                    }
                }
            }
         return null;
    }
    public double getAvatarSpacing(){
        return avatarSpacing;
        
    }
    
    public double getAvatarPosition(){
        return avatarPosition;
    }
    public boolean doneLoading(){
        return loadingDone;
    }
    private void setPosition(double position) {
        this.avatarPosition = position;
        this.damaged = true;
        repaint();
    }
    
    public void setSigma(double sigma) {
        this.sigma = sigma;
        this.rho = 1.0;
        computeEquationParts();
        this.rho = computeModifierUnprotected(0.0);
        computeEquationParts();
        this.damaged = true;
        repaint();
    }
    
    public void setSpacing(double avatarSpacing) {
        if (avatarSpacing < 0.0 || avatarSpacing > 1.0) {
            throw new IllegalArgumentException("Spacing must be < 1.0 and > 0.0");
        }
        this.avatarSpacing = avatarSpacing;
        this.damaged = true;
        repaint();
    }
    
    public void setFilter(HashSet<String> filter)
    {
        this.filter = filter;
      shownAvatars = new Avatar[filter.size()];
      int k = 0;
      for (int i = 0; i < avatars.length; i++)
      {
          Avatar a = avatars[i];
          if (filter.contains(a.artist+a.album))
          {
            shownAvatars[k] = a;
            k++;
          }
      }
        
    }
    public int getAvatarIndex(){
        return avatarIndex;
    }
    @Override
    public Dimension getPreferredSize() {
        return new Dimension(displayWidth * 5, (int) (displayHeight * 3));
    }
    
    @Override
    public Dimension getMinimumSize() {
        return getPreferredSize();
    }
    
    @Override
    public boolean isOpaque() {
        return false;
    }
    
    @Override
    public boolean isFocusable() {
        return true;
    }
    
    @Override
    protected void paintChildren(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;
        
        Composite oldComposite = g2.getComposite();
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
                veilAlphaLevel));
        super.paintChildren(g);
        g2.setComposite(oldComposite);
    }
    
    @Override
    protected void paintComponent(Graphics g) {
        if (!isVisible()) {
            return;
        }
        
        super.paintComponent(g);
        
        if (!loadingDone && faderTimer == null) {
            return;
        }
        
        Insets insets = getInsets();
        
        int x = insets.left;
        int y = insets.top;
        
        int width = getWidth() - insets.left - insets.right;
        int height = getHeight() - insets.top - insets.bottom;
        
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        Composite oldComposite = g2.getComposite();
        
        if (damaged) {
            drawableAvatars = sortAvatarsByDepth(x, y, width, height);
            damaged = false;
        }
        
        drawAvatars(g2, drawableAvatars);
        
        if (drawableAvatars.length > 0) {
            drawAvatarName(g2);
        }
        
        g2.setComposite(oldComposite);
    }
    
    private void drawAvatars(Graphics2D g2, DrawableAvatar[] drawableAvatars) {
        for (DrawableAvatar avatar: drawableAvatars) {
            AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
                    (float) avatar.getAlpha());
            g2.setComposite(composite);
            
            g2.drawImage(avatars[avatar.getIndex()].getImage(),
                    (int) avatar.getX(), (int) avatar.getY(),
                    avatar.getWidth(), avatar.getHeight(), null);
            
            
        }
    }
    
    private DrawableAvatar[] sortAvatarsByDepth(int x, int y,
            int width, int height) {
        List<DrawableAvatar> drawables = new LinkedList<DrawableAvatar>();
        
        for (int i = 0; i < avatars.length; i++) {
           Avatar av = avatars[i];
           if (filter.contains(av.artist+av.album))
           {
            promoteAvatarToDrawable(drawables,
                    x, y, width, height, i - avatarIndex);
           }
        }
        
        DrawableAvatar[] drawableAvatars = new DrawableAvatar[drawables.size()];
        drawableAvatars = drawables.toArray(drawableAvatars);
        Arrays.sort(drawableAvatars);
        
        return drawableAvatars;
    }
    
    private void drawAvatarName(Graphics2D g2) {
        Composite composite = g2.getComposite();
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
                textAlphaLevel));
        
        FontRenderContext context = g2.getFontRenderContext();
        TextLayout layout = new TextLayout(avatarText, avatarFont, context);
        Rectangle2D bounds = layout.getBounds();
        
        double bulletWidth = bounds.getWidth() + 12;
        double bulletHeight = bounds.getHeight() + layout.getDescent() + 4;
        
        double x = (getWidth() - bulletWidth) / 2.0;
        double y = (getHeight() + CD_SIZE) / 2.0;
        
        BufferedImage textImage = new BufferedImage((int) bulletWidth,
                (int) bulletHeight,
                BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2text = textImage.createGraphics();
        g2text.setColor(new Color(0, 0, 0, 170));
        layout.draw(g2text, 6, layout.getAscent() + 1);
        g2text.setColor(Color.WHITE);
        layout.draw(g2text, 6, layout.getAscent());
        g2text.dispose();
        
        g2.drawImage(fx.createReflectedPicture(textImage,
                fx.createGradientMask((int) bulletWidth,
                (int) bulletHeight)),
                (int) x, (int) y, null);
        g2.setComposite(composite);
    }
    
    private void promoteAvatarToDrawable(List<DrawableAvatar> drawables,
            int x, int y, int width, int height,
            int offset) {
        
        double spacing = offset * avatarSpacing;
        double avatarPosition = this.avatarPosition + spacing;
        
        if (avatarIndex + offset < 0 ||
                avatarIndex + offset >= avatars.length) {
            return;
        }
                /*
                try{
                 
                        Image avatar = fx.createReflectedPicture(fx.createCrystalCase(loadCompatibleImage(avatars.get(avatarIndex + offset))));
                }
                catch(IOException e){}
                 */
        
        int avatarWidth = displayWidth;//avatar.getWidth(null);
        int avatarHeight = displayHeight;//avatar.getHeight(null);
        
        double result = computeModifier(avatarPosition);
        int newWidth = (int) (avatarWidth * result);
        if (newWidth == 0) {
            return;
        }
        int newHeight = (int) (avatarHeight * result);
        if (newHeight == 0) {
            return;
        }
        
        double avatar_x = x + (width - newWidth) / 2.0;
        double avatar_y = y + (height - newHeight / 2.0) / 2.0;
        
        double semiWidth = width / 2.0;
        avatar_x += avatarPosition * semiWidth;
        
        if (avatar_x >= width || avatar_x < -newWidth) {
            return;
        }
        
        drawables.add(new DrawableAvatar(avatarIndex + offset,
                avatar_x, avatar_y,
                newWidth, newHeight,
                avatarPosition, result));
    }
    
    private void computeEquationParts() {
        exp_multiplier = Math.sqrt(2.0 * Math.PI) / sigma / rho;
        exp_member = 4.0 * sigma * sigma;
    }
    
    private double computeModifier(double x) {
        double result = computeModifierUnprotected(x);
        if (result > 1.0) {
            result = 1.0;
        } else if (result < -1.0) {
            result = -1.0;
        }
        return result;
    }
    
    private double computeModifierUnprotected(double x) {
        return exp_multiplier * Math.exp((-x * x) / exp_member);
    }
    
   
    

    
    private void loadAvatars() {
        //avatars = new ArrayList<Avatar>();
        
        /*
        picturesFinder = new Thread(new PicturesFinderThread());
        picturesFinder.setPriority(Thread.MIN_PRIORITY);
        picturesFinder.start();
         */
    }
    
    public boolean drawableAvatarsNotNull(){
        return drawableAvatars != null;
    }
    //By Dark_Wolf
    public void loadingDone() {
        loadingDone = true;
        if (downloadImage)
        {
            imageGetter = new Thread(new AudioScrobblerLoaderThread());
            imageGetter.setPriority(Thread.MIN_PRIORITY);
            imageGetter.start();
            
        }
        parentApplet.setSliderValue(avatarIndex);
        
        
    }
    
    
    public boolean getWriteMp3()
    {
        return writeMp3Tag;
    }
    public void setSelectedAlbum(String album, String artist) {
        
        for (int i = 0; i < avatars.length; i++) {
            if ((avatars[i].album.equalsIgnoreCase(album) && avatars[i].artist.equalsIgnoreCase(artist))) {
                setAvatarIndex(i);
                damaged = true;
                repaint();
                return;
            }
            
        }
        System.out.println("setSelectedAlbum: album not found");
    }
    
    
    public String getCurrentAlbum() {
        try {
            return avatars[avatarIndex].getText();
        } catch(Exception e){}
        return "No album selected";
    }
    
    
    public void setAlbumCount(int count) {
        avatars = new Avatar[count];
        this.count = 0;
    }
    
    
    
    public void addImage(String imageURI,String artist,String album) {
        String[] fileNames = {"folder", artist + "-" + album};
        boolean isMP3=false;
        File file=null;
        URI folder =AlbumHelper.songbirdToJavaPath(imageURI);
        URI uri;
        boolean fileNotFound = true;
        Avatar tempAvatar=null;
        //ImageURI redundant??
        if (imageURI.substring(imageURI.lastIndexOf(".")).equalsIgnoreCase("mp3") && ImageTagger.readImage(new File(imageURI))!=null){
            try {

                tempAvatar = new Mp3Avatar(new File(new URI(imageURI)), folder, artist, album, this);
            } catch (URISyntaxException ex) {
               ex.printStackTrace();
            }
            
            
            
        } else {
            
            
            
            for (int i = 0; i < fileNames.length; i++)
                for (int j = 0; j < filetypes.length; j++)
                {
                try{
                    file = AlbumHelper.songbirdToJavaFile(imageURI,fileNames[i], filetypes[j]);
                    
                        
                        if (file.exists() && file.isFile()) {
                            //avatars.add(file);
                            fileNotFound = false;
                            tempAvatar = new FileAvatar(file, folder, artist, album, true, this);
                            break;
                        }
                    
                } catch(Exception e) {
                    e.printStackTrace();
                }
            }
            
            try {
                if (fileNotFound) {
                    uri = AlbumHelper.songbirdToJavaPath(imageURI,"","");
                    if (uri.isAbsolute()) {
                        file = getFirstImage(uri);
                        if (file != null) {
                            file.setReadable(true);
                            file.setExecutable(true);
                            
                            if (file.exists() && file.isFile()) {
                                //avatars.add(file);
                                 tempAvatar = new FileAvatar(file,folder,artist, album, false, this);
                                fileNotFound = false;
                            }
                        }
                    }
                    
                }
                if (file == null) {
                    file = new File(getClass().getResource("/artworks/guitarbird.png").toURI());
                     tempAvatar = new FileAvatar(file,folder, artist, album, false, this);
                    //avatars.add(file);
                }
                
            } catch(Exception e) {
                System.out.println(imageURI);
                
                e.printStackTrace();
                
            }
        }
        avatars[count++]=tempAvatar;
        filter.add(artist+album);
        //avatarsText.add(artist + " - " + album);
        //avatarsArtist.add(artist);
        //avatarsAlbum.add(album);
        
        if (numberOfAvatars++ == avatarAmount) {
            setAvatarIndex(avatarAmount / 2);
            startFader();
           
        }
        
        
        
    }
    
    private File getFirstImage(URI directory) {
        File path = new File(directory);
        FilenameFilter filter = new FilenameFilter() {
            public boolean accept(File dir,String name) {
                boolean accept = false;
                for (String s : filetypes)
                    accept = accept || name.endsWith("."+s);
                return accept;
            }
        };
        if (path.isDirectory()) {
            String[] files = path.list(filter);
            if (files.length != 0) {
                
                return new File(path.getPath()+"\\"+files[0]);
            }
            
        }
        return null;
    }
    
 
    public void setAvatarIndex(int index) {
        avatarIndex = index;
        avatarText = avatars[index].getText();
        
        runLoader = false;
        picturesLoader = new Thread(new PicturesLoaderThread());
        picturesLoader.setPriority(Thread.MIN_PRIORITY);
        runLoader = true;
        picturesLoader.start();
        
        
        
        
        
    }
    
    
    
    
    
    
    
   
    
    public DrawableAvatar getHitAvatar(int x, int y) {
        for (DrawableAvatar avatar: drawableAvatars) {
            Rectangle hit = new Rectangle((int) avatar.getX(), (int) avatar.getY(),
                    avatar.getWidth(), avatar.getHeight() / 2);
            if (hit.contains(x, y)) {
                return avatar;
            }
        }
        return null;
    }
    
    private void startFader() {
        faderTimer = new Timer(35, new FaderAction());
        faderTimer.start();
    }
    
    
    
    private class PicturesLoaderThread implements Runnable {
        
        
        
        
        public void run() {
            for (int i = 0; i < avatars.length; i++) {
                if (!runLoader)
                    break;
                
                if (Math.abs(avatarIndex-i) < 20)
                    avatars[i].loadImage();
                else if (avatars[i] != null)
                    try {
                        avatars[i].unloadImage();
                    } catch(Exception e) {
                        e.printStackTrace();
                        System.out.println("Error in album number: " + i);
                    }
                
                
                
            }
        }
    }
    
    private class AudioScrobblerLoaderThread implements Runnable {
        
        
        
        
        public void run() {
            for (int i = 0; i < avatars.length; i++) {
                
                avatars[i].downLoadImage();
                
                
                
            }
        }
    }
    
    private class FaderAction implements ActionListener {
        private long start = 0;
        
        private FaderAction() {
            alphaLevel = 0.0f;
            textAlphaLevel = 0.0f;
        }
        
        public void actionPerformed(ActionEvent e) {
            if (start == 0) {
                start = System.currentTimeMillis();
            }
            
            alphaLevel = (System.currentTimeMillis() - start) / 500.0f;
            textAlphaLevel = alphaLevel;
            if (alphaLevel > 1.0f) {
                alphaLevel = 1.0f;
                textAlphaLevel = 1.0f;
                faderTimer.stop();
            }
            
            repaint();
        }
    }
    
    public class DrawableAvatar implements Comparable {
        private int index;
        private double x;
        private double y;
        private int width;
        private int height;
        private double zOrder;
        private double position;
        
        private DrawableAvatar(int index,
                double x, double y, int width, int height,
                double position, double zOrder) {
            this.index = index;
            this.x = x;
            this.y = y;
            this.width = width;
            this.height = height;
            this.position = position;
            this.zOrder = zOrder;
        }
        
        public int compareTo(Object o) {
            double zOrder2 = ((DrawableAvatar) o).zOrder;
            if (zOrder < zOrder2) {
                return -1;
            } else if (zOrder > zOrder2) {
                return 1;
            }
            return 0;
        }
        
        public double getPosition() {
            return position;
        }
        
        public double getAlpha() {
            return zOrder * alphaLevel;
        }
        
        public int getHeight() {
            return height;
        }
        
        public int getIndex() {
            return index;
        }
        
        public int getWidth() {
            return width;
        }
        
        public double getX() {
            return x;
        }
        
        public double getY() {
            return y;
        }
    }
    
    
    
   
    
    private class DamageManager extends ComponentAdapter {
        @Override
        public void componentResized(ComponentEvent e) {
            damaged = true;
        }
    }
    private class sliderScroller implements ActionListener {
        private double position;
        private int index;
        private long start;
        
        private sliderScroller(int index) {
            this.index = index;
            this.position = (index-avatarIndex)*avatarSpacing+avatarPosition;
            this.start = System.currentTimeMillis();
        }
        
        public void actionPerformed(ActionEvent e) {
            long elapsed = System.currentTimeMillis() - start;
            if (elapsed < ANIM_SCROLL_DELAY / 2.0) {
                textAlphaLevel = (float) (1.0 - 2.0 * (elapsed / ANIM_SCROLL_DELAY));
            } else {
                avatarText = avatars[index].getText();
                textAlphaLevel = (float) (((elapsed / ANIM_SCROLL_DELAY) - 0.5) * 2.0);
                if (textAlphaLevel > 1.0f) {
                    textAlphaLevel = 1.0f;
                }
            }
            if (textAlphaLevel < 0.1f) {
                textAlphaLevel = 0.1f;
            }
            double newPosition = (elapsed / ANIM_SCROLL_DELAY) * -position;
            
            if (elapsed >= ANIM_SCROLL_DELAY) {
                ((Timer) e.getSource()).stop();
                setAvatarIndex(index);
                setPosition(0.0);
                return;
            }
            
            setPosition(newPosition);
        }
    } 
  
    
      private class AutoScroller implements ActionListener {
        private double position;
        private int index;
        private long start;
        
        private AutoScroller(CDShelf.DrawableAvatar avatar) {
            this.index = avatar.getIndex();
            this.position = avatar.getPosition();
            this.start = System.currentTimeMillis();
        }
        
        public void actionPerformed(ActionEvent e) {
            long elapsed = System.currentTimeMillis() - start;
            if (elapsed < ANIM_SCROLL_DELAY / 2.0) {
                textAlphaLevel = (float) (1.0 - 2.0 * (elapsed / ANIM_SCROLL_DELAY));
            } else {
                avatarText = avatars[index].getText();
                textAlphaLevel = (float) (((elapsed / ANIM_SCROLL_DELAY) - 0.5) * 2.0);
                if (textAlphaLevel > 1.0f) {
                    textAlphaLevel = 1.0f;
                }
            }
            if (textAlphaLevel < 0.1f) {
                textAlphaLevel = 0.1f;
            }
            double newPosition = (elapsed / ANIM_SCROLL_DELAY) * -position;
            
            if (elapsed >= ANIM_SCROLL_DELAY) {
                ((Timer) e.getSource()).stop();
                setAvatarIndex(index);
                setPosition(0.0);
                return;
            }
            
            setPosition(newPosition);
        }
    }
    
    
 
   
    

}